package edu.kit.ksri.db.mturk.connectionhandlers;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

import org.apache.log4j.Logger;

import com.amazonaws.mturk.addon.HITDataCSVReader;
import com.amazonaws.mturk.addon.HITDataCSVWriter;
import com.amazonaws.mturk.addon.HITDataInput;
import com.amazonaws.mturk.addon.HITDataOutput;
import com.amazonaws.mturk.addon.HITProperties;
import com.amazonaws.mturk.addon.HITQuestion;
import com.amazonaws.mturk.requester.Assignment;
import com.amazonaws.mturk.requester.HIT;
import com.amazonaws.mturk.service.axis.RequesterService;
import com.amazonaws.mturk.service.exception.ServiceException;
import com.amazonaws.mturk.util.PropertiesClientConfig;

import edu.kit.ksri.db.mturk.var.ConnectionMode;
import edu.kit.ksri.db.mturk.var.StaticInformation;

/**
 * Copyright (c) 2010-2011 Karlsruhe Institute of Technology (KIT), Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * --------------------------------------------------------------------------
 * 
 * Software: CSP/WMV tool for dynamic quality management of microtasks
 * http://www.ksri.kit.edu/Upload/Users/PeopleClouds/HCOMP2011_An_extendable_toolkit_for_managing_quality_of_human-based_eServices.pdf
 * 
 * Organization: Karlsruhe Institute of Technology (KIT), Germany
 * http://www.kit.edu
 * 
 * Project: PeopleClouds project of the Karlsruhe Service Research Institute(KSRI)
 * http://www.ksri.kit.edu/Default.aspx?PageId=818
 * 
 * Contributing authors: David Bermbach, Sandra Rath, Pascal Wichmann, Robert Kern
 */
public class HITManager extends AbstractHITManager {

	private static Logger basiclog = Logger.getLogger("basic." + HITManager.class);

	public boolean hasEnoughFund() {
		RequesterService service = new RequesterService(
				new PropertiesClientConfig(StaticInformation.mturkFile));
		double balance = service.getAccountBalance();
		System.out.println("Got account balance: "
				+ RequesterService.formatCurrency(balance));
		return balance > 0;
	}

	protected boolean extendHitInternal(String hitId) {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			service.extendHIT(hitId, 1, null);
			return true;
		} catch (ServiceException e) {
			e.printStackTrace();
			return false;
		}
	}

	public double accountBalance() {
		RequesterService service = new RequesterService(
				new PropertiesClientConfig(StaticInformation.mturkFile));
		double balance = service.getAccountBalance();
		System.out.println("Got account balance: "
				+ RequesterService.formatCurrency(balance));
		return balance;
	}

	public boolean deleteHit(String hitid) {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			service.disposeHIT(hitid);
			return true;
		} catch (ServiceException e) {
			e.printStackTrace();
			return false;
		}
	}

	public boolean approveAssignment(String assignmentid, String feedback) {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			service.approveAssignment(assignmentid, feedback);
			return true;
		} catch (ServiceException e) {
			return false;
		}
	}

	public boolean rejectAssignment(String assignmentid, String feedback) {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			service.rejectAssignment(assignmentid, feedback);
			return true;
		} catch (ServiceException e) {
			e.printStackTrace();
			return false;
		}
	}

	public int getNumberOfCompletedHITs(String hittypeid) {
		if (hittypeid.equals("startvalue")) {
			return 0;
		}
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			return service.getAllReviewableHITs(hittypeid).length;
		} catch (ServiceException e) {
			e.printStackTrace();
			return -1;
		}

	}

	public String[] getAllHITIDs() {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			HIT[] temp = service.searchAllHITs();
			String[] res = new String[temp.length];
			int counter = 0;
			for (HIT h : temp) {
				res[counter++] = h.getHITId();
			}
			return res;
		} catch (ServiceException e) {
			e.printStackTrace();
			return new String[0];
		}
	}

	public HIT[] getAllHITs(){
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			HIT[] hits = service.searchAllHITs();
			return hits;
		}
		catch (ServiceException e) {
			e.printStackTrace();
			return null;
		}
	}

	public HIT[] getAllHITsForHITType(){
		HIT[] hits = this.getAllHITs();
		ArrayList<HIT> h = new ArrayList<HIT>(); 
		
		//Sort the HITs out that don't belong to the current HIT type id
		for(HIT hit : hits){
			if(hit.getKeywords().equals(StaticInformation.scenario.getHitKeywords()))
				h.add(hit);
		}
		
		//Do we have any HITs for this HIT type id left?
		if(h.size() != 0){
			HIT[] hitsForType = new HIT[h.size()];

			for(int i = 0; i < h.size(); i++){
				hitsForType[i] = h.get(i);
			}
			
			return hitsForType;
		}
		else{
			//No hits for this HIT type existing
			return null;
		}
	}

	@Override
	public String[] getAllReviewableHITIDs() {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			HIT[] temp = service
					.getAllReviewableHITs(StaticInformation.scenario.getHitTypeID());
			String[] res = new String[temp.length];
			int counter = 0;
			for (HIT h : temp) {
				res[counter++] = h.getHITId();
			}
			return res;
		} catch (ServiceException e) {
			e.printStackTrace();
			return new String[0];
		}
	}

	public Assignment[] getAssignmentsForHITID(String hitid) {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			return service.getAllAssignmentsForHIT(hitid);
		} catch (ServiceException e) {
			e.printStackTrace();
			return new Assignment[0];
		}
	}

	public int getNumberOfHITs(String hittypeid) {
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			return service.searchAllHITs().length;
		} catch (ServiceException e) {
			e.printStackTrace();
			return -1;
		}
	}

	@Override
	protected HIT[] createHITsInternal(String inputFile, String questionFile,
			String propertiesFile) {
		if(StaticInformation.scenario.getHITCreationType().equals("Batch")){
			return createHITsBatch(inputFile, questionFile, propertiesFile);
		}
		else if(StaticInformation.scenario.getHITCreationType().equals("Manually")){
			return createHITsManually(inputFile, questionFile, propertiesFile);
		}
		else{
			return null;
		}
	}

	/**
	 * @see edu.kit.ksri.db.mturk.connectionhandlers.AbstractHITManager#
	 *      createHITsInternal (java.lang.String, java.lang.String,
	 *      java.lang.String)
	 * 
	 *      uses the createHITs method - will sometimes malfunction
	 * 
	 * @param inputFile
	 * @param questionFile
	 * @param propertiesFile
	 * @return
	 */
	private HIT[] createHITsBatch(String inputFile, String questionFile,
			String propertiesFile) {
		HIT[] hits = null;
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			// Loading the input file. The input file is a tab delimited file
			// where the first row
			// defines the fields/variables and the remaining rows contain the
			// values for each HIT.
			// Each row represents a unique HIT. The SDK uses the Apache
			// Velocity engine to merge
			// the input values into a templatized QAP file.
			// Please refer to http://velocity.apache.org for more details on
			// this engine and
			// how to specify variable names. Apache Velocity is fully supported
			// so there may be
			// additional functionality you can take advantage of if you know
			// the Velocity syntax.
			HITDataInput input = new HITDataCSVReader(inputFile);
			// Loading the question (QAP) file. This QAP file contains Apache
			// Velocity variable names where the values
			// from the input file are to be inserted. Essentially the QAP
			// becomes a template for the input file.
			HITQuestion question = new HITQuestion(questionFile);
			// Loading the HIT properties file. The properties file defines two
			// system qualifications that will
			// be used for the HIT. The properties file can also be a Velocity
			// template. Currently, only
			// the "annotation" field is allowed to be a Velocity template
			// variable. This allows the developer
			// to "tie in" the input value to the results.
			HITProperties props = new HITProperties(propertiesFile);
			// Create multiple HITs using the input, properties, and question
			// files
			basiclog.info("Creating HITs...");
			// The simplest way to bulk load a large number of HITs where all
			// details are defined in files.
			// When using this method, it will automatically create output files
			// of the following type:
			// - <your input file name>.success - A file containing the HIT IDs
			// and HIT Type IDs of
			// all HITs that were successfully loaded. This file will
			// not exist if there are no HITs successfully loaded.
			// - <your input file name>.failure - A file containing the input
			// rows that failed to load.
			// This file will not exist if there are no failures.
			// The .success file can be used in subsequent operations to
			// retrieve the results that workers submitted.
			HITDataOutput success = new HITDataCSVWriter(inputFile + ".success");
			HITDataOutput failure = new HITDataCSVWriter(inputFile + ".failure");
			hits = service.createHITs(input, props, question, success, failure);
			if(StaticInformation.scenario.getConnectionMode() == ConnectionMode.CORRSIMINPUT){
				// Correctness Simulation Input Mode: Fixed HIT Count is necessary
				
				String[] hitIds = new String[hits.length];
				int i = 0;
				for(HIT h : hits){
					hitIds[i] = h.getHITId();
					i++;
				}
				
				service.extendHITs(hitIds, StaticInformation.scenario.getSimulationHitCount()-1, null, null);
			}
			basiclog.info("Finished creating HITs.");
		} catch (Exception e) {
			basiclog.error("Exception while creating HITs: " + e);
		}
		return hits;
	}

	/**
	 * @see edu.kit.ksri.db.mturk.connectionhandlers.AbstractHITManager#
	 *      createHITsInternal (java.lang.String, java.lang.String,
	 *      java.lang.String)
	 * 
	 *      uses the createHIT method - needs to be changed when the OCR
	 *      scenario is not used
	 * 
	 * @param inputFile
	 * @param questionFile
	 * @param propertiesFile
	 * @return
	 */
	private HIT[] createHITsManually(String inputFile, String questionFile,
			String propertiesFile) {
		HIT[] hits = null;
		try {
			RequesterService service = new RequesterService(
					new PropertiesClientConfig(StaticInformation.mturkFile));
			String question = "";
			BufferedReader br = new BufferedReader(new InputStreamReader(
					new FileInputStream(StaticInformation.questionFile)));
			String line = br.readLine();
			while (line != null) {
				question += line;
				line = br.readLine();
			}
			ArrayList<String> urls = new ArrayList<String>();
			br = new BufferedReader(new InputStreamReader(new FileInputStream(
					StaticInformation.inputFile)));
			line = br.readLine();
			while (line != null) {
				if (!line.trim().equals("") && !line.trim().equals("urls"))
					urls.add(line.trim());
				line = br.readLine();
			}
			hits = new HIT[urls.size()];
			String questionInput = null;
			int counter = 0;
			for (String url : urls) {
				questionInput = question.replace("$urls", url);
				try {
					hits[counter] = service.createHIT(
							StaticInformation.scenario.getHitTypeID(), null, null, null,
							questionInput, null, null, null,
							StaticInformation.scenario.getTimeUntilHitExpiration(), null,
							null, null, null);
					if(StaticInformation.scenario.getConnectionMode() == ConnectionMode.CORRSIMINPUT){
						// Correctness Simulation Input Mode: Fixed HIT Count is necessary
						service.extendHIT(hits[counter].getHITId(), StaticInformation.scenario.getSimulationHitCount()-1, null);
					}
				} catch (ServiceException e) {
					basiclog.error("Error while connecting to MTurk: " + e);
				}
				counter++;
				if (hits[counter - 1] != null)
					basiclog.info("Uploaded HIT for URL " + url);
				else
					basiclog.warn("Could not create HIT for URL " + url);
			}
			basiclog.info("Finished creating HITs.");
		} catch (Exception e) {
			basiclog.error("Exception while creating HITs: " + e);
		}
		return hits;
	}
}
